﻿using System;
using System.Configuration;
using System.Linq;
using System.Reflection;

using mvcForum.Core.Configuration;
using mvcForum.Core.Interfaces;
using mvcForum.Core.Interfaces.Search;
using mvcForum.Web.Interfaces;

using MVCThemes.Interfaces;
using mvcForum.Core.Interfaces.DependencyManagement;
using mvcForum.Core.Interfaces.Events;
using mvcForum.Core.Interfaces.Services;

namespace mvcForum.Web {

	public class ApplicationConfiguration {
		public readonly IDependencyContainer container;

		protected ApplicationConfiguration() { }

		private ApplicationConfiguration Configure() {
			// Let's get the MVC Forum section from the config file!
			MVCForumSection config = MVCForumSection.Get(ConfigurationManager.OpenExeConfiguration(""));
			// Did we get it? And is it present in the config file?
			if (config == null || (config != null && config.ElementInformation.IsPresent == false)) {
				// No, fail!
				throw new ConfigurationException("No MVC Forum configuration section found");
			}
			// DI container
			if (String.IsNullOrWhiteSpace(config.DependencyContainerBuilder.Type)) {
				throw new ConfigurationException("No type found for dependencyContainerBuilder");
			}
			IDependencyContainer container = this.CreateContainer(config);
			// Database configuration
			this.ExecuteBuilder(config.DatabaseBuilder.Type, container, "databaseBuilder");
			// Storage configuration
			this.ExecuteBuilder(config.StorageBuilder.Type, container, "storageBuilder");
			// Logging configuration
			this.Bind(config.LoggingProviderComponent, container, typeof(ILogger));

			// Let's configure any other builders included.
			foreach (NamedComponent builder in config.AdditionalBuilders) {
				this.ExecuteBuilder(builder.Type, container, "additionalBuilder");
			}

			// And let's wire up the search builders.
			foreach (NamedComponent builder in config.SearchBuilders) {
				this.ExecuteBuilder(builder.Type, container, "searchBuilder");
			}

			// Theme
			this.Bind(config.ThemeProviderComponent, container, typeof(IThemeProvider));
			this.Bind(config.ThemeUrlProviderComponent, container, typeof(IThemeURLProvider));

			// Event publishing
			this.Bind(config.EventPublisherComponent, container, typeof(IEventPublisher));
			this.Bind(config.AsyncTaskComponent, container, typeof(IAsyncTask));

			// Url provider.
			this.Bind(config.UrlProviderComponent, container, typeof(IURLProvider));

			// Infrastructure
			this.Bind(config.MailServiceComponent, container, typeof(IMailService));
			this.Bind(config.MembershipServiceComponent, container, typeof(mvcForum.Core.Interfaces.Services.IMembershipService));
			this.Bind(config.FormsAuthenticationComponent, container, typeof(mvcForum.Core.Interfaces.Services.IAuthenticationService));
			this.Bind(config.UserProviderComponent, container, typeof(IWebUserProvider));

			// Listeners
			this.Bind(config.EventListenerComponents, container, typeof(IEventListener));
			// Content Parsers
			this.Bind(config.ContentParserComponents, container, typeof(IContentParser));

			IWebUserProvider up = (IWebUserProvider)System.Web.Mvc.DependencyResolver.Current.GetService(typeof(IWebUserProvider));

			return this;
		}

		private void Bind(NamedComponentsElementCollection components, IDependencyContainer container, Type bindTo) {
			foreach (NamedComponent component in components) {
				String type;
				String assembly;
				this.GetAssemblyAndType(component.Type, out assembly, out type);
				container.RegisterGeneric(bindTo, this.GetType(assembly, type));
			}
		}

		private void Bind(UniqueComponentElement component, IDependencyContainer container, Type bindTo) {
			String type;
			String assembly;
			if (String.IsNullOrWhiteSpace(component.Type)) {
				throw new ArgumentException(String.Format("No configuration found for type {0}", bindTo.Name));
			}
			this.GetAssemblyAndType(component.Type, out assembly, out type);
			container.RegisterGeneric(bindTo, this.GetType(assembly, type));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="assembly"></param>
		/// <param name="type"></param>
		/// <returns></returns>
		/// <exception cref="System.ArgumentException">If the assembly or type was not found.</exception>
		private Type GetType(String assembly, String type) {
			// Let's get the assemblies in the AppDomain!
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			// Find the one we're looking for.
			Assembly ass = assemblies.Where(a => a.GetName().Name == assembly).FirstOrDefault();
			// Found it?
			if (ass == null) {
				// No, fail!
				throw new ArgumentException(String.Format("Assembly '{0}' not found.", assembly));
			}
			// Let's find the type we're looking for.
			Type t = ass.GetTypes().Where(ty => ty.FullName == type).FirstOrDefault();
			// Found it?
			if (t == null) {
				// No, fail!
				throw new ArgumentException(String.Format("Type '{0}' not found.", type));
			}

			// Done deal!
			return t;
		}

		private void ExecuteBuilder(String typeString, IDependencyContainer container, String builderName) {
			if (String.IsNullOrWhiteSpace(typeString)) {
				throw new ConfigurationException(String.Format("No type found for {0}", builderName));
			}
			String assembly = String.Empty;
			String type = String.Empty;
			// Get assembly and type string!
			this.GetAssemblyAndType(typeString, out assembly, out type);

			// Get the type!
			Type t = this.GetType(assembly, type);

			// Create an instance,
			IDependencyBuilder builder = (IDependencyBuilder)Activator.CreateInstance(t, false);
			// and configure it!
			builder.Configure(container);
		}

		private IDependencyContainer CreateContainer(MVCForumSection config) {
			String assembly = String.Empty;
			String type = String.Empty;
			this.GetAssemblyAndType(config.DependencyContainerBuilder.Type, out assembly, out type);

			Type t = this.GetType(assembly, type);

			IDependencyContainer container = (IDependencyContainer)Activator.CreateInstance(t, false);
			container.Configure();

			return container;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="input"></param>
		/// <param name="assembly"></param>
		/// <param name="type"></param>
		/// <exception cref="System.ArgumentNullException">If the input argument is empty.</exception>
		/// <exception cref="System.ArgumentException">If the input argument is not in the correct format, 'full type path, assembly'</exception>
		private void GetAssemblyAndType(String input, out String assembly, out String type) {
			if (String.IsNullOrWhiteSpace(input)) {
				throw new ArgumentNullException("input");
			}
			String[] parts = input.Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			if (parts.Length != 2) {
				throw new ArgumentException("input");
			}
			assembly = parts[1].Trim();
			type = parts[0].Trim();
		}

		private static Boolean initialized = false;
		private static Object objectLock = new Object();
		public static ApplicationConfiguration Initialize() {
			if (!initialized) {
				lock (objectLock) {
					if (!initialized) {
						ApplicationConfiguration config = new ApplicationConfiguration();
						return config.Configure();
					}
				}
			}
			throw new ApplicationException("Already initialized");
		}
	}
}